package com.rskj.cashbook.util;

import com.rskj.cashbook.iexception.BusinessException;
import com.rskj.cashbook.vo.UserInfo;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.HashMap;
import com.rskj.cashbook.iexception.TokenExpiredException;


@Slf4j
@Component
public class JwtUtil {

    @Resource
    RedisUtil redisUtil;

    @Value("${jwt.expired.time:604800000}")
    private long EXPIRED_TIME;

    public static final String TOKEN_PREFIX = "TOKEN:";

    private static final String SECRET_KEY = "123456789"; // 使用更复杂的密钥

    /**
     * 生成设备ID，基于请求信息创建稳定的设备标识
     * 优先使用前端传递的设备ID，如果没有则基于请求信息生成
     */
    private String generateDeviceId(HttpServletRequest request, Map<String, Object> claims) {
        // 1. 优先使用前端传递的设备ID
        String frontendDeviceId = (String) claims.get("frontendDeviceId");
        if (StringUtils.isNotBlank(frontendDeviceId)) {
            return frontendDeviceId;
        }
        
        // 2. 如果没有前端设备ID，则基于请求信息生成
        StringBuilder deviceInfo = new StringBuilder();
        
        // 用户代理信息
        String userAgent = request.getHeader("User-Agent");
        if (StringUtils.isNotBlank(userAgent)) {
            deviceInfo.append(userAgent.hashCode());
        }
        
        // IP地址（作为备用标识）
        String remoteAddr = getClientIpAddress(request);
        if (StringUtils.isNotBlank(remoteAddr)) {
            deviceInfo.append(":").append(remoteAddr.hashCode());
        }
        
        // 添加时间戳（确保唯一性）
        deviceInfo.append(":").append(System.currentTimeMillis());
        
        // 生成MD5哈希作为设备ID
        return org.apache.commons.codec.digest.DigestUtils.md5Hex(deviceInfo.toString());
    }

    /**
     * 获取客户端真实IP地址
     */
    private String getClientIpAddress(HttpServletRequest request) {
        String xForwardedFor = request.getHeader("X-Forwarded-For");
        if (StringUtils.isNotBlank(xForwardedFor)) {
            return xForwardedFor.split(",")[0].trim();
        }
        
        String xRealIp = request.getHeader("X-Real-IP");
        if (StringUtils.isNotBlank(xRealIp)) {
            return xRealIp;
        }
        
        return request.getRemoteAddr();
    }

    /**
     * 生成Redis key，使用username:deviceId格式
     */
    public static String getTokenKey(String username, String deviceId) {
        return TOKEN_PREFIX + username + ":" + deviceId;
    }

    /**
     * 生成Redis key，使用username:sessionId格式（兼容旧版本）
     */
    public static String getTokenKeyBySession(String username, String sessionId) {
        return TOKEN_PREFIX + username + ":" + sessionId;
    }

    public String generateToken(UserInfo userInfo, Map<String, Object> claims, HttpServletRequest request) throws BusinessException {
        try {
            if (userInfo == null || StringUtils.isBlank(userInfo.getUserName())) {
                throw new BusinessException("UserInfo or username is null");
            }
            
            String deviceId = generateDeviceId(request, claims);
            
            claims.put("deviceId", deviceId);
            claims.put("loginTime", System.currentTimeMillis());
            claims.put("userAgent", request.getHeader("User-Agent"));
            claims.put("loginIp", getClientIpAddress(request));
            
            String token = Jwts.builder()
                    .setClaims(claims)
                    .setSubject(userInfo.getUserName())
                    .setIssuedAt(new Date())
                    .setExpiration(new Date(System.currentTimeMillis() + EXPIRED_TIME))
                    .signWith(SignatureAlgorithm.HS256, SECRET_KEY)
                    .compact();
                    
            String redisKey = getTokenKey(userInfo.getUserName(), deviceId);
            redisUtil.set(redisKey, token, 7, TimeUnit.DAYS);
            
            log.info("Generated token for user: {}, deviceId: {}, redisKey: {}", 
                    userInfo.getUserName(), deviceId, redisKey);
            return token;
        } catch (Exception e) {
            log.error("Error generating token: {}", e.getMessage(), e);
            throw new BusinessException("Token generation failed: " + e.getMessage());
        }
    }

    // 从JWT中提取用户名
    public String extractUsername(String token) throws BusinessException {
        try {
            if (StringUtils.isBlank(token)) {
                log.warn("Token is null or blank");
                return null;
            }
            
            String username = extractClaim(token, Claims::getSubject);
            log.debug("Extracted username from token: {}", username);
            
            if (StringUtils.isBlank(username)) {
                log.warn("Username extracted from token is null or blank");
                return null;
            }
            
            return username;
        } catch (Exception e) {
            log.error("Error extracting username from token: {}", e.getMessage(), e);
            throw new BusinessException("Failed to extract username: " + e.getMessage());
        }
    }

    // 从JWT中提取设备ID
    public String extractDeviceId(String token) throws BusinessException {
        try {
            if (StringUtils.isBlank(token)) {
                log.warn("Token is null or blank when extracting deviceId");
                return null;
            }
            
            String deviceId = extractClaim(token, claims -> claims.get("deviceId", String.class));
            log.debug("Extracted deviceId from token: {}", deviceId);
            return deviceId;
        } catch (Exception e) {
            log.error("Error extracting deviceId from token: {}", e.getMessage(), e);
            return null;
        }
    }

    // 从JWT中提取特定的claim
    public <T> T extractClaim(String token, java.util.function.Function<Claims, T> claimsResolver) throws BusinessException {
        try {
            final Claims claims = extractAllClaims(token);
            if (claims == null) {
                log.warn("Claims extracted from token is null");
                return null;
            }
            return claimsResolver.apply(claims);
        } catch (Exception e) {
            log.error("Error extracting claim from token: {}", e.getMessage(), e);
            throw new BusinessException("Failed to extract claim: " + e.getMessage());
        }
    }

    // 从JWT中解析所有的claim
    public Claims extractAllClaims(String token) throws BusinessException {
        try {
            if (StringUtils.isBlank(token)) {
                log.warn("Token is null or blank when extracting claims");
                return null;
            }
            
            Claims claims = Jwts.parser()
                    .setSigningKey(SECRET_KEY)
                    .parseClaimsJws(token)
                    .getBody();
                    
            log.debug("Successfully extracted claims from token");
            return claims;
        } catch (ExpiredJwtException e) {
            log.warn("JWT token has expired: {}", e.getMessage());
            throw new TokenExpiredException("Token has expired");
        } catch (UnsupportedJwtException e) {
            log.error("Unsupported JWT token: {}", e.getMessage());
            throw new BusinessException("Unsupported JWT token");
        } catch (MalformedJwtException e) {
            log.error("Malformed JWT token: {}", e.getMessage());
            throw new BusinessException("Malformed JWT token");
        } catch (SignatureException e) {
            log.error("Invalid JWT signature: {}", e.getMessage());
            throw new BusinessException("Invalid JWT signature");
        } catch (IllegalArgumentException e) {
            log.error("JWT token is empty: {}", e.getMessage());
            throw new BusinessException("JWT token is empty");
        } catch (Exception e) {
            log.error("Unexpected error parsing JWT token: {}", e.getMessage(), e);
            throw new BusinessException("Unexpected error parsing JWT token: " + e.getMessage());
        }
    }

    /**
     * 
     * @param token
     * @return
     * @throws BusinessException
     */
    public boolean isTokenExpired(String token) throws BusinessException {
        try {
            return extractExpiration(token).before(new Date());
        } catch (Exception e) {
            log.error("Error checking token expiration: {}", e.getMessage(), e);
            return true; // 如果无法检查，认为已过期
        }
    }

    // 从JWT中提取过期时间
    public Date extractExpiration(String token) throws BusinessException {
        try {
            return extractClaim(token, Claims::getExpiration);
        } catch (Exception e) {
            log.error("Error extracting expiration from token: {}", e.getMessage(), e);
            throw new BusinessException("Failed to extract expiration: " + e.getMessage());
        }
    }

    // 验证JWT是否有效
    public boolean validateToken(String token, String username) throws BusinessException {
        try {
            if (StringUtils.isBlank(token) || StringUtils.isBlank(username)) {
                log.warn("Token or username is null/blank during validation");
                return false;
            }
            
            String extractedUsername = extractUsername(token);
            boolean isValid = (username.equals(extractedUsername) && !isTokenExpired(token));
            log.debug("Token validation result: {} for user: {}", isValid, username);
            return isValid;
        } catch (Exception e) {
            log.error("Error validating token: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取用户的所有活跃token数量
     */
    public long getUserActiveTokenCount(String username) {
        try {
            String pattern = TOKEN_PREFIX + username + ":*";
            return redisUtil.getKeyCount(pattern);
        } catch (Exception e) {
            log.error("Error getting user active token count: {}", e.getMessage(), e);
            return 0;
        }
    }

    /**
     * 强制用户下线（删除所有token）
     */
    public boolean forceUserLogout(String username) {
        try {
            String pattern = TOKEN_PREFIX + username + ":*";
            redisUtil.deleteByPattern(pattern);
            log.info("Forced logout for user: {}", username);
            return true;
        } catch (Exception e) {
            log.error("Error forcing user logout: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 删除特定设备的token
     */
    public boolean logoutDevice(String username, String deviceId) {
        try {
            String redisKey = getTokenKey(username, deviceId);
            redisUtil.delete(redisKey);
            log.info("Logged out device {} for user: {}", deviceId, username);
            return true;
        } catch (Exception e) {
            log.error("Error logging out device: {}", e.getMessage(), e);
            return false;
        }
    }

    /**
     * 获取用户的所有活跃设备信息
     */
    public List<Map<String, Object>> getUserActiveDevices(String username) {
        try {
            String pattern = TOKEN_PREFIX + username + ":*";
            Set<String> keys = redisUtil.getKeysByPattern(pattern);
            
            if (keys == null || keys.isEmpty()) {
                return new ArrayList<>();
            }
            
            List<Map<String, Object>> devices = new ArrayList<>();
            
            for (String key : keys) {
                try {
                    // 从key中提取deviceId
                    String deviceId = key.substring(key.lastIndexOf(":") + 1);
                    
                    // 获取token
                    String token = redisUtil.get(key).toString();
                    if (StringUtils.isNotBlank(token)) {
                        // 从token中提取设备信息
                        Map<String, Object> deviceInfo = new HashMap<>();
                        deviceInfo.put("deviceId", deviceId);
                        
                        // 提取登录时间
                        try {
                            Long loginTime = extractClaim(token, claims -> claims.get("loginTime", Long.class));
                            deviceInfo.put("loginTime", loginTime != null ? loginTime : System.currentTimeMillis());
                        } catch (Exception e) {
                            deviceInfo.put("loginTime", System.currentTimeMillis());
                        }
                        
                        // 提取用户代理
                        try {
                            String userAgent = extractClaim(token, claims -> claims.get("userAgent", String.class));
                            deviceInfo.put("userAgent", userAgent);
                        } catch (Exception e) {
                            deviceInfo.put("userAgent", "未知");
                        }
                        
                        // 提取登录IP
                        try {
                            String loginIp = extractClaim(token, claims -> claims.get("loginIp", String.class));
                            deviceInfo.put("loginIp", loginIp);
                        } catch (Exception e) {
                            deviceInfo.put("loginIp", "未知");
                        }
                        
                        // 设置最后活跃时间（当前时间）
                        deviceInfo.put("lastActive", System.currentTimeMillis());
                        
                        devices.add(deviceInfo);
                    }
                } catch (Exception e) {
                    log.warn("Error processing device key {}: {}", key, e.getMessage());
                }
            }
            
            log.info("Found {} active devices for user: {}", devices.size(), username);
            return devices;
            
        } catch (Exception e) {
            log.error("Error getting user active devices: {}", e.getMessage(), e);
            return new ArrayList<>();
        }
    }
}
